﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UltimatePlugin.Db.Cache;
using UltimatePlugin.Technicals;
using Zeta;
using Zeta.Common;
using Zeta.Internals.Actors;

namespace UltimatePlugin.Db.SpellManagement
{
    /// <summary>
    /// Manage SNOPower delays and usages.
    /// </summary>
    internal static class SpellManager
    {
        private static readonly IDictionary<SNOPower, PowerInfo> _PowerInfos = new Dictionary<SNOPower, PowerInfo>();
        private static readonly IList<ScheduledSpell> _ScheduledPowers = new List<ScheduledSpell>();
        private static Thread _ScheduleThread;

        /// <summary>Defines the delay of power.</summary>
        /// <param name="power">The power.</param>
        /// <param name="info">The delay.</param>
        /// <exception cref="System.InvalidOperationException">Delay can't be negative</exception>
        public static void DefineDelay(SNOPower power, PowerInfo info)
        {
            if (info != null)
            {
                throw new ArgumentNullException();
            }
            if (_PowerInfos.ContainsKey(power))
            {
                _PowerInfos[power] = info;
            }
            else
            {
                _PowerInfos.Add(power, info);
            }
        }

        /// <summary>
        /// Determines if Power can be used.
        /// </summary>
        /// <param name="power">The power to check.</param>
        /// <returns><c>true</c> if Power can be used; otherwise, <c>false</c>.</returns>
        public static bool CanUse(SNOPower power)
        {
            return  HeroCache.Energy[_PowerInfos[power].Energy] > _PowerInfos[power].Cost && 
                    DateTime.UtcNow.Subtract(_PowerInfos[power].LastUsage).TotalMilliseconds >= _PowerInfos[power].Delay * (HeroCache.HasEmpowered ? 0.75 : 1) && 
                    Zeta.CommonBot.PowerManager.CanCast(power);
        }

        /// <summary>
        /// Uses the specified power.
        /// </summary>
        /// <param name="power">The power to use.</param>
        /// <param name="clickPos">The position to click.</param>
        /// <param name="worldDynamicId">The world dynamic id.</param>
        /// <param name="targetACDGuid">The target ACDGuid.</param>
        public static void Use(SNOPower power, Vector3 clickPos, int worldDynamicId = -1, int targetACDGuid = -1)
        {
            if (_PowerInfos.ContainsKey(power))
            {
                _PowerInfos[power].LastUsage = DateTime.UtcNow;
            }

            ZetaDia.Me.UsePower(power, clickPos, worldDynamicId, targetACDGuid);
        }

        /// <summary>
        /// Schedules the power.
        /// </summary>
        /// <param name="power">The power to use.</param>
        /// <param name="delay">The delay between 2 usages.</param>
        /// <param name="condition">The condition call before use power.</param>
        public static void Schedule(SNOPower power, int delay, Func<SNOPower, bool> condition)
        {
            lock (_ScheduledPowers)
            {
                if (_ScheduledPowers.Count < 1)
                {
                    _ScheduleThread = new Thread(MonitorPower);
                    _ScheduleThread.Priority = ThreadPriority.BelowNormal;
                    _ScheduleThread.Start();
                }
                else if (_ScheduledPowers.Any(sp => sp.Power == power))
                {
                    Logger.Write(LogSeverity.Error, LogCategory.Casting, "You try to schedule 2 times same Power.");
                    return;
                }
                _ScheduledPowers.Add(new ScheduledSpell()
                {
                    Power = power,
                    Delay = delay,
                    LastUsage = DateTime.MinValue,
                    Condition = condition
                });
            }
        }

        /// <summary>
        /// Unschedules the specified power.
        /// </summary>
        /// <param name="power">The power must be remove from schedule table.</param>
        public static void Unschedule(SNOPower power)
        {
            lock (_ScheduledPowers)
            {
                _ScheduledPowers.Remove(new ScheduledSpell() { Power = power });

                if (_ScheduledPowers.Count < 1)
                {
                    _ScheduleThread.Abort();
                    _ScheduleThread = null;
                }
            }
        }

        private static void MonitorPower(object obj)
        {
            while (true)
            {
                lock (_ScheduledPowers)
                {
                    foreach (ScheduledSpell power in _ScheduledPowers.Where(sp => DateTime.UtcNow.Subtract(sp.LastUsage).TotalMilliseconds >= sp.Delay && CanUse(sp.Power)))
                    {
                        if (power.Condition(power.Power))
                        {
                            power.LastUsage = DateTime.UtcNow;
                            Use(power.Power, Vector3.Zero);
                        }
                    }
                }
                Thread.Sleep(250);
            }
        }
    }
}
